// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包同步提供基本同步原语，如相互
// 排除锁。除了Once和WaitGroup类型之外，大多数都是供低级库例程使用的
// 类型。更高级别的同步是
// 最好通过通道和通信实现。
// 
// 不应复制包含此包中定义的类型的值。
package sync

import (
	"internal/race"
	"sync/atomic"
	"unsafe"
)

func throw(string) // 由运行时提供

// 互斥锁是互斥锁。
// 互斥体的零值是未锁定的互斥体。
// 
// 第一次使用后不得复制互斥锁。
type Mutex struct {
	state int32
	sema  uint32
}

// 储物柜代表可以锁定和解锁的对象。
type Locker interface {
	Lock()
	Unlock()
}

const (
	mutexLocked = 1 << iota // 互斥锁已锁定
	mutexWoken
	mutexStarving
	mutexWaiterShift = iota

	// 互斥公平性。
	// 
	// 互斥可以有两种操作模式：正常和饥饿。
	// 在正常模式下，服务员按FIFO顺序排队，但醒来的服务员
	// 不拥有互斥锁，并与新到达的goroutines争夺
	// 所有权。新到的goroutines有一个优势——它们已经在CPU上运行，而且可能有很多，所以一个醒来的服务员很有可能会输。在这种情况下，它在等待队列的前面
	// 排队。如果服务生在超过1ms的时间内无法获取互斥，
	// 它会将互斥切换到饥饿模式。
	// 
	// 在饥饿模式下，互斥锁的所有权直接从
	// 解锁goroutine传递给队列前面的服务员。
	// 新到达的goroutines不要尝试获取互斥锁，即使它看起来被解锁，也不要尝试旋转。相反，他们在等待队列的尾部排队。
	// 
	// 如果服务生收到互斥锁的所有权，并看到
	// （1）它是队列中的最后一个服务生，或者（2）它等待的时间不到1毫秒，
	// 它将互斥锁切换回正常操作模式。
	// 
	// 正常模式的性能要好得多，因为goroutine可以连续多次获得
	// 互斥，即使有被阻止的服务员。
	// 饥饿模式对于预防尾潜伏期的病理病例非常重要。
	starvationThresholdNs = 1e6
)

// Lock locks m.
// 如果锁已经在使用，则调用的goroutine 
// 将阻塞，直到互斥锁可用。shujian defg
func (m *Mutex) Lock() {
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		if race.Enabled {
			race.Acquire(unsafe.Pointer(m))
		}
		return
	}
	// Slow path（勾勒出这样的轮廓以便快速路径可以内联）
	m.lockSlow()
}

// TryLock尝试锁定m并报告是否成功。注意，虽然TryLock的正确用法确实存在，但它们很少见，TryLock的使用通常是一个更深层次问题的标志。
func (m *Mutex) TryLock() bool {
	old := m.state
	if old&(mutexLocked|mutexStarving) != 0 {
		return false
	}

	// 可能有一个goroutine正在等待互斥锁，但我们现在正在运行，可以在该goroutine醒来之前尝试获取互斥锁。
	if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
		return false
	}

	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
	return true
}

func (m *Mutex) lockSlow() {
	var waitStartTime int64
	starving := false
	awoke := false
	iter := 0
	old := m.state
	for {
		// 不要在饥饿模式下旋转，所有权交给服务员
		// 所以我们无论如何都无法获得互斥。
		if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
			// 主动旋转是有意义的。
			// 尝试设置MUTEXWAKEN标志，通知解锁
			// 不要唤醒其他被阻止的Goroutine。
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
				awoke = true
			}
			runtime_doSpin()
			iter++
			old = m.state
			continue
		}
		new := old
		// 不要试图获得饥饿的互斥，新到达的goroutine必须排队。
		if old&mutexStarving == 0 {
			new |= mutexLocked
		}
		if old&(mutexLocked|mutexStarving) != 0 {
			new += 1 << mutexWaiterShift
		}
		// 当前的goroutine将互斥切换到饥饿模式。
		// 但如果互斥锁当前已解锁，请不要切换。
		// Unlock期望饥饿的互斥对象有服务员，但在这种情况下，这不是真的。
		if starving && old&mutexLocked != 0 {
			new |= mutexStarving
		}
		if awoke {
			// goroutine已从睡眠中唤醒，
			// 因此，无论哪种情况，我们都需要重置标志。
			if new&mutexWoken == 0 {
				throw("sync: inconsistent mutex state")
			}
			new &^= mutexWoken
		}
		if atomic.CompareAndSwapInt32(&m.state, old, new) {
			if old&(mutexLocked|mutexStarving) == 0 {
				break // 用CAS锁定互斥锁
			}
			// 如果我们之前已经在等待，请在队列前面排队。
			queueLifo := waitStartTime != 0
			if waitStartTime == 0 {
				waitStartTime = runtime_nanotime()
			}
			runtime_SemacquireMutex(&m.sema, queueLifo, 1)
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
			old = m.state
			if old&mutexStarving != 0 {
				// 如果此goroutine被唤醒且mutex处于饥饿模式，则
				// 所有权已移交给我们，但mutex处于某种程度上
				// 不一致的状态：mutexLocked未设置，我们仍被视为服务员。解决这个问题。
				if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
					throw("sync: inconsistent mutex state")
				}
				delta := int32(mutexLocked - 1<<mutexWaiterShift)
				if !starving || old>>mutexWaiterShift == 1 {
					// 退出饥饿模式。ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊
					// 饥饿模式效率太低，以至于两个goroutine 
					// 一旦他们将互斥
					// 切换到饥饿模式，就可以无限锁定步长。
					delta -= mutexStarving
				}
				atomic.AddInt32(&m.state, delta)
				break
			}
			awoke = true
			iter = 0
		} else {
			old = m.state
		}
	}

	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
}

// 解锁解锁m。
// 如果m在要解锁的条目上未被锁定，则这是一个运行时错误。
// 
// 锁定的互斥锁与特定的goroutine不关联。
// 允许一个goroutine锁定互斥锁，然后
// 安排另一个goroutine解锁互斥锁。
func (m *Mutex) Unlock() {
	if race.Enabled {
		_ = m.state
		race.Release(unsafe.Pointer(m))
	}

	// 快速路径：丢弃锁位。
	new := atomic.AddInt32(&m.state, -mutexLocked)
	if new != 0 {
		// 概述了慢路径，以允许内联快速路径。
		// 为了在跟踪过程中隐藏unlockSlow，我们在跟踪GoUnblock时跳过了一个额外的帧。
		m.unlockSlow(new)
	}
}

func (m *Mutex) unlockSlow(new int32) {
	if (new+mutexLocked)&mutexLocked == 0 {
		throw("sync: unlock of unlocked mutex")
	}
	if new&mutexStarving == 0 {
		old := new
		for {
			// 如果没有服务员或goroutine已经被叫醒或抓住锁，则无需叫醒任何人。
			// 在饥饿模式下，所有权直接从解锁
			// goroutine传递给下一个服务员。我们不是这个链的一部分，因为我们在解锁上面的互斥时没有观察到互斥启动。
			// 那就让开。
			if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
				return
			}
			// 抓住叫醒某人的权利。
			new = (old - 1<<mutexWaiterShift) | mutexWoken
			if atomic.CompareAndSwapInt32(&m.state, old, new) {
				runtime_Semrelease(&m.sema, false, 1)
				return
			}
			old = m.state
		}
	} else {
		// 饥饿模式：将互斥对象的所有权移交给下一个服务员，并产生
		// 我们的时间片，以便下一个服务员可以立即开始运行。
		// 注意：mutexLocked没有设置，服务员会在醒来后设置。
		// 但如果设置了mutexStarving，则互斥锁仍被视为已锁定，因此新的goroutines将不会获得它。
		runtime_Semrelease(&m.sema, true, 1)
	}
}
